#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Reflection;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// Definitions of built in Yarr functions
	/// </summary>
	internal static partial class Functions
	{

		/// <summary>
		/// (eq expression*) 
        ///  Compares objects
		/// </summary>
		/// <param name="args">A list of expressions to compare</param>
		/// <param name="environment">The local environment</param>
        /// <returns>Returns true if all expressions are reference equal, 
        /// that is they refer to the same object in memory. As a special case, null is eq to null.
        /// </returns>
        [Function("eq","(&rest args)")]
		public static Object Eq(Environment environment) 
		{
            return Primitives.Eq((Cons)environment.GetValue(SYSTEM, "args"));
		}


		/// <summary>
		/// (eql expression*) 
        /// Compares objects
		/// </summary>
		/// <param name="args">A list of objects to compare</param>
		/// <param name="environment">The local environment</param>
        /// <returns>Returns true if all expressions are equal, that is 
        /// their implementations of Equal return true. Assumes that the
        /// implementations of Equal are transitive.
        /// As a special case, null is eql to null. 
        /// </returns>
		[Function("eql;==", "(&rest args)")]
        public static Object Eql(Environment environment) 
		{
            return Primitives.Eql((Cons)environment.GetValue(SYSTEM, "args"));
		}


		/// <summary>
		/// (> number1 number2 [number3 ...])
        /// Determine if a list of numbers is strictly decreasing
        /// Each argument must be a numeric type, such as System.In32 or System.Double
        /// </summary>
		/// <param name="args">A list of numbers to check</param>
		/// <param name="environment">The local environment</param>
		/// <returns> Returns true if number1 is greater than object2,
        /// object2 is greater than object3, etc. Othewise returns false.
        /// </returns>
		[Function(">", "(arg0 &rest args)")]
        public static Object GreaterThan(Environment environment) 
		{
            object last = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null || args.Count == 0)
                throw new YarrException("GreaterThan: Expecting at least two arguments");

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType = TypeCode.Empty;
            TypeCode bType = Type.GetTypeCode(Primitives.TypeOf(last));

			foreach (object item in args) 
			{
                aType = bType;
                bType = Type.GetTypeCode(Primitives.TypeOf(item));

                if (Primitives.IsNumber(aType)  &&  Primitives.IsNumber(bType))
                {
                    if (!GreaterThanHelper(last, item, aType, bType))
                        return false;
                }

                else if ((last is IComparable) && 
                         (item is IComparable) && 
                         (Primitives.TypeOf(last) == Primitives.TypeOf(item)))
                {
                    if (((IComparable)last).CompareTo(item) <= 0)
                        return false;
                }

                else
                {
                    Type t0 = Primitives.TypeOf(last);
                    Type t1 = Primitives.TypeOf(item);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_greaterthan", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "GreaterThan: No greater-than operator defined for ({0},{1})", 
                                t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = last;
                    a[1] = item;

                    if (!(bool)mi.Invoke(t[0], a))
                        return false;
                }

				last = item;
			}

			return true;
		}

        private static bool GreaterThanHelper (object a, object b, TypeCode aType, TypeCode bType)
        {
            try
            {
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) > (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Single || bType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) > (Single)Convert.ChangeType(b, typeof(Single));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) > (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) > (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) > (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) > (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) > (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)Convert.ChangeType(a, typeof(UInt16)) > (UInt16)Convert.ChangeType(b, typeof(UInt16));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)Convert.ChangeType(a, typeof(Char)) > (Char)Convert.ChangeType(b, typeof(Char));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)Convert.ChangeType(a, typeof(Int16)) > (Int16)Convert.ChangeType(b, typeof(Int16));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (Byte)Convert.ChangeType(a, typeof(Byte)) > (Byte)Convert.ChangeType(b, typeof(Byte));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)Convert.ChangeType(a, typeof(SByte)) > (SByte)Convert.ChangeType(b, typeof(SByte));
                else
                    throw new YarrException("GreaterThan: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("GreaterThan: arguments must be numeric");
            }
        }


		/// <summary>
		/// (>= number1 number2 [number3 ...]) 
        /// Determines if a list of numbers is monotonically decreasing
        /// Each argument must be a numeric type, such as System.In32 or System.Double
        /// </summary>
		/// <param name="args">The list of numbers to check</param>
		/// <param name="environment">The local environment</param>
        /// <returns>Returns true if number1 is greater 
        /// than or eql to number2, number2 is greater than or eql to number3, etc.
        /// Otherwise returns false
        /// </returns>
		[Function(">=", "(arg0 &rest args)")]
        public static Object GreaterThanEqual(Environment environment) 
		{
            object last = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null || args.Count == 0)
                throw new YarrException("GreaterThanOrEqual: Expecting at least two arguments");

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType = TypeCode.Empty;
            TypeCode bType = Type.GetTypeCode(Primitives.TypeOf(last));

            foreach (object item in args)
            {
                aType = bType;
                bType = Type.GetTypeCode(Primitives.TypeOf(item));

                if (Primitives.IsNumber(aType)  &&  Primitives.IsNumber(bType))
                {
                    if (!GreaterThanEqualHelper(last, item, aType, bType))
                        return false;
                }

                else if ((last is IComparable) && 
                         (item is IComparable) && 
                         (Primitives.TypeOf(last) == Primitives.TypeOf(item)))
                {
                    if (((IComparable)last).CompareTo(item) < 0)
                        return false;
                }

                else
                {
                    Type t0 = Primitives.TypeOf(last);
                    Type t1 = Primitives.TypeOf(item);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_greaterthanorequal", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "GreaterThanEqual: No greater-than-or-equal operator defined for ({0},{1})", 
                                t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = last;
                    a[1] = item;

                    if (!(bool)mi.Invoke(t[0], a))
                        return false;
                }

                last = item;
            }

            return true;
        }

        private static bool GreaterThanEqualHelper (object a, object b, TypeCode aType, TypeCode bType)
        {
            try
            {
                if (aType == TypeCode.Double || bType == TypeCode.Double)
                    return (Double)Convert.ChangeType(a, typeof(Double)) >= (Double)Convert.ChangeType(b, typeof(Double));

                else if (aType == TypeCode.Single || bType == TypeCode.Single)
                    return (Single)Convert.ChangeType(a, typeof(Single)) >= (Single)Convert.ChangeType(b, typeof(Single));

                else if (aType == TypeCode.Decimal || bType == TypeCode.Decimal)
                    return (Decimal)Convert.ChangeType(a, typeof(Decimal)) >= (Decimal)Convert.ChangeType(b, typeof(Decimal));

                else if (aType == TypeCode.UInt64 || bType == TypeCode.UInt64)
                    return (UInt64)Convert.ChangeType(a, typeof(UInt64)) >= (UInt64)Convert.ChangeType(b, typeof(UInt64));

                else if (aType == TypeCode.Int64 || bType == TypeCode.Int64)
                    return (Int64)Convert.ChangeType(a, typeof(Int64)) >= (Int64)Convert.ChangeType(b, typeof(Int64));

                else if (aType == TypeCode.UInt32 || bType == TypeCode.UInt32)
                    return (UInt32)Convert.ChangeType(a, typeof(UInt32)) >= (UInt32)Convert.ChangeType(b, typeof(UInt32));

                else if (aType == TypeCode.Int32 || bType == TypeCode.Int32)
                    return (Int32)Convert.ChangeType(a, typeof(Int32)) >= (Int32)Convert.ChangeType(b, typeof(Int32));

                else if (aType == TypeCode.UInt16 || bType == TypeCode.UInt16)
                    return (UInt16)Convert.ChangeType(a, typeof(UInt16)) >= (UInt16)Convert.ChangeType(b, typeof(UInt16));

                else if (aType == TypeCode.Char || bType == TypeCode.Char)
                    return (Char)Convert.ChangeType(a, typeof(Char)) >= (Char)Convert.ChangeType(b, typeof(Char));

                else if (aType == TypeCode.Int16 || bType == TypeCode.Int16)
                    return (Int16)Convert.ChangeType(a, typeof(Int16)) >= (Int16)Convert.ChangeType(b, typeof(Int16));

                else if (aType == TypeCode.Byte || bType == TypeCode.Byte)
                    return (Byte)Convert.ChangeType(a, typeof(Byte)) >= (Byte)Convert.ChangeType(b, typeof(Byte));

                else if (aType == TypeCode.SByte || bType == TypeCode.SByte)
                    return (SByte)Convert.ChangeType(a, typeof(SByte)) >= (SByte)Convert.ChangeType(b, typeof(SByte));
                else
                    throw new YarrException("GreaterThanEqual: arguments must be numeric");
            }
            catch (System.InvalidCastException)
            {
                throw new YarrException("GreaterThanEqual: arguments must be numeric");
            }
        }


		/// <summary>
		/// (&lt; number1 number2 [number3 ...])
        /// Determines if a list of numbers is strictly increasing
        /// Each argument must be a numeric type, such as System.In32 or System.Double
		/// </summary>
		/// <param name="args">A list of numbers</param>
		/// <param name="environment">The local environment</param>
        /// <returns>Returns true if number1 is less than number2, number2 is less than number3, etc.
        /// Ottherwise returns false.
        /// </returns>
		[Function("<", "(arg0 &rest args)")]
        public static Object LessThan(Environment environment) 
		{
            object last = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null || args.Count == 0)
                throw new YarrException("LessThan: Expecting at least two arguments");
            
            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType = TypeCode.Empty;
            TypeCode bType = Type.GetTypeCode(Primitives.TypeOf(last));

            foreach (object item in args)
            {
                aType = bType;
                bType = Type.GetTypeCode(Primitives.TypeOf(item));

                if (Primitives.IsNumber(aType)  &&  Primitives.IsNumber(bType))
                {
                    if (!GreaterThanHelper(item, last, bType, aType))
                        return false;
                }

                else if ((last is IComparable) && 
                         (item is IComparable) && 
                         (Primitives.TypeOf(last) == Primitives.TypeOf(item)))
                {
                    if  (((IComparable)last).CompareTo(item) >= 0)
                        return false;
                }

                else
                {
                    Type t0 = Primitives.TypeOf(last);
                    Type t1 = Primitives.TypeOf(item);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_lessthan", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "LessThan: No less-than operator defined for ({0},{1})", 
                                t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = last;
                    a[1] = item;

                    if (!(bool)mi.Invoke(t[0], a))
                        return false;
                }

                last = item;
            }
            return true;
        }


		/// <summary>
		/// (&lt;= number1 number2 [number3 ...])
        /// Determines if a list of numbers is monotonically increasing
        /// Each argument must be a numeric type, such as System.In32 or System.Double
        /// </summary>
		/// <param name="args">A list of numbers</param>
		/// <param name="environment">The local environment</param>
        /// <returns>Returns true if number1 is less than or eql number2, 
        /// number2 is less than or eql number3, etc. Ottherwise returns false.
        /// </returns>
        [Function("<=", "(arg0 &rest args)")]
        public static Object LessThanEqual(Environment environment) 
		{
            object last = environment.GetValue(SYSTEM, "arg0");
            Cons args = (Cons)environment.GetValue(SYSTEM, "args");

            if (args == null || args.Count == 0)
                throw new YarrException("LessThanOrEqual: Expecting at least two arguments");

            MethodInfo mi = null;
            Type[] t = null;
            Object[] a = null;
            TypeCode aType = TypeCode.Empty;
            TypeCode bType = Type.GetTypeCode(Primitives.TypeOf(last));

            foreach (object item in args)
            {
                aType = bType;
                bType = Type.GetTypeCode(Primitives.TypeOf(item));

                if (Primitives.IsNumber(aType)  &&  Primitives.IsNumber(bType))
                {
                    if (!GreaterThanEqualHelper(item, last, bType, aType))
                        return false;
                }

                else if ((last is IComparable) && 
                         (item is IComparable) && 
                         (Primitives.TypeOf(last) == Primitives.TypeOf(item)))
                {
                    if (((IComparable)last).CompareTo(item) > 0)
                        return false;
                }

                else
                {
                    Type t0 = Primitives.TypeOf(last);
                    Type t1 = Primitives.TypeOf(item);
                    if (t == null  ||  t[0] != t0  ||  t[1] != t1)
                    {
                        t = new Type[2];
                        t[0] = t0;
                        t[1] = t1;
                        mi = null;
                    }

                    if (mi == null)
                        mi = MethodCache.FindMethod("op_lessthanorequal", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                    if (mi == null)
                        throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                "LessThanEqual: No less-than-or-equal operator defined for ({0},{1})",
                                t[0], t[1]));

                    if (a == null)
                        a = new Object[2];

                    a[0] = last;
                    a[1] = item;

                    if (!(bool)mi.Invoke(t[0], a))
                        return false;
                }

                last = item;
            }
            return true;
        }


		/// <summary>
		/// (not bool-expr) 
        /// Returns the logical complement of the given boolean 
		/// </summary>
		/// <param name="args">A list containing a boolean</param>
		/// <param name="environment">The local environment</param>
		/// <returns>If the argument is true, returns false. Otherwise returns true.</returns>
		[Function("not", "(&optional arg)")]
        public static Object Not(Environment environment) 
		{
            return !Conversions.ObjectToBoolean(environment.GetValue(SYSTEM, "arg"));
		}


		/// <summary>
		/// (!= expression*) 
        /// The logical complement of the eql operator, equivalent to (not (eql expression*))
		/// </summary>
		/// <param name="args">A list of objects</param>
		/// <param name="environment">The local environment</param>
		/// <returns>If all of the objects are eql, returns false. Otherwise returns true.</returns>
        [Function("!=", "(&rest args)")]
		public static Object NotEql(Environment environment) 
		{
            return !Primitives.Eql((Cons)environment.GetValue(SYSTEM, "args"));
		}
	}
}
